package com.faithbj.process.stats;

import com.faithbj.common.utils.StateEndTimeUtil;
import com.faithbj.process.*;
import com.faithbj.support.fp.Tuple2;
import com.faithbj.support.fp.Tuple3;
import com.faithbj.support.fp.Tuple4;
import com.faithbj.support.fp.Tuple6;
import com.google.common.base.Function;
import com.google.common.collect.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * User: xiongyuanwei
 * Date: 12-7-15
 * Time: 下午2:18
 */
@Service
public class JbpmStatsServiceImpl implements JbpmStatsService {

    @Autowired
    private ProcessQueryService processQueryService;

    @Autowired
    private StreetRegionIdsConditionFactory streetRegionIdsConditionFactory;

    @Override
    public Map<String, Tuple6<State, String, Date, Date, Date, Date>> getApplyDetailsByApplyIds(List<String> applyIds) {
        ApplyIdsCondition condition = new ApplyIdsCondition();
        condition.setApplyIds(Sets.newHashSet(applyIds));

        // 获取第一个节点任务的applyId进行匹配，查出指定applyIds对应的流程号作为group key
        // K: applyId
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(condition, State.NEW_ARRIVAL,
                0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));

        // 填充multimap，使之延伸到所有节点（之前只有任务受理节点）
        List<Long> taskIdsInFirstState = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));

        // K: taskId
        final Map<Long, Long> taskId2PiIdCache = processQueryService.getProcessInstanceIdsByTaskIds(taskIdsInFirstState);

        // K: piId
        Map<Long, String> piId2ApplyIdCache = Maps.newHashMap();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            String applyId = (String) entry.getKey();
            TaskModel task = entry.getValue();
            piId2ApplyIdCache.put(taskId2PiIdCache.get(task.getId()), applyId);
        }

        // K: piId
        Multimap<Long, TaskModel> piTasks = processQueryService.getTaskModelsByProcessInstanceIds(Lists.newArrayList(piId2ApplyIdCache.keySet()));

        // K: taskId
        Map<Long, String> taskId2ApplyIdCache = Maps.newHashMap();

        // 填充applyId -> Tasks multimap
        for (Map.Entry<Long, TaskModel> entry : piTasks.entries()) {
            TaskModel task = entry.getValue();
            String applyId = piId2ApplyIdCache.get(entry.getKey());
            groupedTasks.put(applyId, task);
            taskId2ApplyIdCache.put(task.getId(), applyId);
        }

        // K: state
        Map<Tuple2<String, State>, TaskModel> state2TaskCache = Maps.newHashMap();

        // K: taskId
        Map<Long, State> taskId2StateCache = Maps.newHashMap();

        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            String applyId = (String) entry.getKey();
            TaskModel task = entry.getValue();
            State state = StateFactory.getStateByTaskName(task.getName());
            state2TaskCache.put(new Tuple2<String, State>(applyId, state), task);
            taskId2StateCache.put(task.getId(), StateFactory.getStateByTaskName(task.getName()));
        }

        Set<Long> taskIds = taskId2StateCache.keySet();

        // K: taskId
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(Lists.newArrayList(taskIds));

        // 构建当前任务节点缓存
        Map<String, State> currentStateCache = Maps.newHashMap();
        for (Map.Entry<Long, StartAndEndDateOfTaskTuple> entry : datesMap.entrySet()) {
            Long taskId = entry.getKey();
            StartAndEndDateOfTaskTuple t = entry.getValue();
            if (t.isTaskActive()) {
                if (taskId2ApplyIdCache.containsKey(taskId)) {
                    String applyId = taskId2ApplyIdCache.get(taskId);
                    State state = taskId2StateCache.get(taskId);
                    currentStateCache.put(applyId, state);
                }
            }
        }

        Map<String, Tuple6<State, String, Date, Date, Date, Date>> ret = Maps.newHashMap();

        for (Map.Entry<Object, Collection<TaskModel>> entry : groupedTasks.asMap().entrySet()) {
            String applyId = (String) entry.getKey(); // 申请号
            State state0 = currentStateCache.get(applyId); // 当前状态
            TaskModel task1 = state2TaskCache.get(new Tuple2<String, State>(applyId, State.FIRST_CHECK_MODIFICATION)); // 首核节点
            String user1 = task1 == null ? null : task1.getUser(); // 首核责任人名字
            TaskModel task2 = state2TaskCache.get(new Tuple2<String, State>(applyId, State.NEW_ARRIVAL)); // 业务受理节点
            Date date1 = null; // 受理日期
            if (task2 != null) {
                StartAndEndDateOfTaskTuple t = datesMap.get(task2.getId());
                if (t != null) {
                    date1 = t.getEndDate();
                }
            }
            TaskModel task3 = state2TaskCache.get(new Tuple2<String, State>(applyId, State.VERIFICATION)); // 街镇审核节点
            Date date2 = null; // 街镇审核日期
            if (task3 != null) {
                StartAndEndDateOfTaskTuple t = datesMap.get(task3.getId());
                if (t != null) {
                    date2 = t.getEndDate();
                }
            }
            TaskModel task4 = state2TaskCache.get(new Tuple2<String, State>(applyId, State.IMPORT)); // 导入内网节点
            Date date3 = null; // 导入内网日期
            if (task4 != null) {
                StartAndEndDateOfTaskTuple t = datesMap.get(task4.getId());
                if (t != null) {
                    date3 = t.getEndDate();
                }
            }
            TaskModel task0 = state2TaskCache.get(new Tuple2<String, State>(applyId, state0));
            Date date0 = null; // 当前状态的起始日期
            if (task0 != null) {
                StartAndEndDateOfTaskTuple t = datesMap.get(task0.getId());
                if (t != null) {
                    date0 = t.getStartDate();
                }
            }
            ret.put(applyId, new Tuple6<State, String, Date, Date, Date, Date>(state0, user1, date1, date2, date3, date0));
        }
        return ret;
    }

    @Override
    public Map<String, Map<State, Long>> countGroupByApplyStateInRegion(String regionId, Date from, Date to) {
        StreetRegionIdsCondition condition = streetRegionIdsConditionFactory.create(regionId);
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(
                condition, State.NEW_ARRIVAL, 0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));
        List<Long> taskIdsInFirstState = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));
        final Map<Long, Long> taskId2PiIdCache = processQueryService.getProcessInstanceIdsByTaskIds(taskIdsInFirstState);
        Map<Long, String> piId2RegionIdCache = Maps.newHashMap();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            String rid = (String) entry.getKey();
            TaskModel task = entry.getValue();
            piId2RegionIdCache.put(taskId2PiIdCache.get(task.getId()), rid);
        }
        Multimap<Long, TaskModel> piTasks = processQueryService.getTaskModelsByProcessInstanceIds(Lists.newArrayList(taskId2PiIdCache.values()));
        for (Map.Entry<Long, TaskModel> entry : piTasks.entries()) {
            TaskModel task = entry.getValue();
            String rid = piId2RegionIdCache.get(entry.getKey());
            groupedTasks.put(rid, task);
        }
        Set<Long> taskIds = Sets.newHashSet();
        for (Map.Entry<Long, TaskModel> entry : piTasks.entries()) {
            taskIds.add(entry.getValue().getId());
        }
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(Lists.newArrayList(taskIds));
        Map<String, Map<State, Long>> ret = Maps.newHashMap();
        for (Map.Entry<Object, Collection<TaskModel>> entry : groupedTasks.asMap().entrySet()) {
            String rid = (String) entry.getKey();
            Collection<TaskModel> tasks = entry.getValue();
            Multiset<State> states = HashMultiset.create();
            for (TaskModel task : tasks) {
                StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
                Date operationDate = t.getStartDate();
                boolean isCurrent = t.getEndDate() == null;
                if (isCurrent && operationDate != null && isDateSatisfied(operationDate, from, to)) {
                    State state = StateFactory.getStateByTaskName(task.getName());
                    states.add(state);
                }
            }
            Map<State, Long> stateCounts = Maps.newHashMap();
            for (Multiset.Entry<State> e : states.entrySet()) {
                stateCounts.put(e.getElement(), (long) e.getCount());
            }
            ret.put(rid, stateCounts);
        }
        return ret;
    }

    @Override
    public Map<String, Tuple4<Long, Long, Long, Long>> countReportsGroupByStreetInRegion(String regionId, Date from, Date to) {
        StreetRegionIdsCondition condition = streetRegionIdsConditionFactory.create(regionId);
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(
                condition, State.SECOND_CHECK, 0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));
        List<Long> taskIds = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));
        Set<Long> piIds = Sets.newHashSet(processQueryService.getProcessInstanceIdsByTaskIds(taskIds).values());
        Set<String> nodeNames = ImmutableSet.of(State.REPORT.getNodeName());
        Table<Long, State, Tuple2<Date, Date>> table = processQueryService.getStartAndEndDatesByProcessInstancesAndStates(piIds, nodeNames);
        Map<String, Tuple4<Long, Long, Long, Long>> ret = Maps.newHashMap();
        for (Map.Entry<Object, Collection<TaskModel>> entry : groupedTasks.asMap().entrySet()) {
            String rid = (String) entry.getKey();
            Collection<TaskModel> tasks = entry.getValue();
            long n0 = 0, n1 = 0, n2 = 0, n3 = 0;
            for (TaskModel task : tasks) {
                Tuple2<Date, Date> dates = table.get(task.getProcessInstanceId(), State.REPORT);
                if (dates != null && dates._2() != null && isDateSatisfied(dates._2(), from, to)) {
                    int checkResult = (Integer) task.get("checkResult");
                    switch (checkResult) {
                        case 0: n0++; break;
                        case 1: n0++; n1++; break;
                        case 2: n0++; n2++; break;
                        case 3: n0++; n3++; break;
                    }
                    ret.put(rid, new Tuple4<Long, Long, Long, Long>(n0, n1, n2, n3));
                }
            }
        }
        return ret;
    }

    @Override
    public Map<PauseReason, Long> getPauseReasonDetailsInRegion(String regionId, Date from, Date to) {
        StreetRegionIdsCondition condition = streetRegionIdsConditionFactory.create(regionId);
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(
                condition, State.INTERRUPTED, 0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));
        List<Long> taskIds = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(taskIds);
        Multiset<PauseReason> reasons = HashMultiset.create();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            TaskModel task = entry.getValue();
            StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
            if (t != null && t.getStartDate() != null && isDateSatisfied(t.getStartDate(), from, to)) {
                PauseReason pauseReason = PauseReason.valueOf((String) task.get("pauseReason"));
                reasons.add(pauseReason);
            }
        }
        Map<PauseReason, Long> ret = Maps.newHashMap();
        for (Multiset.Entry<PauseReason> entry : reasons.entrySet()) {
            ret.put(entry.getElement(), (long) entry.getCount());
        }
        return ret;
    }

    @Override
    public Map<TerminateReason, Long> getTerminateReasonDetailsInRegion(String regionId, Date from, Date to) {
        StreetRegionIdsCondition condition = streetRegionIdsConditionFactory.create(regionId);
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(
                condition, State.TERMINATION_CHECK, 0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));
        List<Long> taskIds = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(taskIds);
        Multiset<TerminateReason> reasons = HashMultiset.create();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            TaskModel task = entry.getValue();
            StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
            if (t != null && t.getStartDate() != null && isDateSatisfied(t.getStartDate(), from, to)) {
                TerminateReason terminateReason = TerminateReason.valueOf((String) task.get("terminateReason"));
                reasons.add(terminateReason);
            }
        }
        Map<TerminateReason, Long> ret = Maps.newHashMap();
        for (Multiset.Entry<TerminateReason> entry : reasons.entrySet()) {
            ret.put(entry.getElement(), (long) entry.getCount());
        }
        return ret;
    }

    @Override
    public Map<String, Map<State, List<Tuple3<Date, Date, Date>>>> getTimingDetailsInVerifyPhaseDeadlineDuring(Date from, Date to) {
        List<TaskModel> allTasks = processQueryService.listAllTasksByStates(Arrays.asList(
                State.VERIFICATION, State.FIRST_CHECK_MODIFICATION, State.SECOND_CHECK));
        List<Long> taskIds = Lists.transform(allTasks, new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel task) {
                return task.getId();
            }
        });
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(Lists.newArrayList(taskIds));
        Map<String, Map<State, List<Tuple3<Date, Date, Date>>>> ret = Maps.newHashMap();
        for (TaskModel task : allTasks) {
            StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
            if (t != null) {
                State state = StateFactory.getStateByTaskName(task.getName());
                Date createDate = task.getCreatedOn();
                Date deadline = StateEndTimeUtil.getEndDate(state.getId(), createDate);
                Date operationDate = t.getEndDate();
                Date comparedDate = operationDate == null ? new Date() : operationDate;
                if (deadline != null && deadline.before(comparedDate)) {
                    String userId = task.getUser() == null ? "UNASSIGNED" : task.getUser();
                    Map<State, List<Tuple3<Date, Date, Date>>> map = ret.get(userId);
                    if (map == null) {
                        map = Maps.newHashMap();
                        List<Tuple3<Date, Date, Date>> list = Lists.newLinkedList();
                        list.add(new Tuple3<Date, Date, Date>(createDate, deadline, operationDate));
                        map.put(state, list);
                    } else {
                        List<Tuple3<Date, Date, Date>> list = map.get(state);
                        list.add(new Tuple3<Date, Date, Date>(createDate, deadline, operationDate));
                    }
                    ret.put(userId, map);
                }
            }
        }
        return ret;
    }

    @Override
    public Map<String, Map<State, List<Tuple3<Date, Date, Date>>>> getTimingDetailsInInputPhaseDeadlineDuring(String regionId, Date from, Date to) {
        StreetRegionIdsCondition condition = streetRegionIdsConditionFactory.create(regionId);
        SetMultimap<Object, TaskModel> groupedTasks = HashMultimap.create(processQueryService.groupedSearchAll(
                condition, State.NEW_ARRIVAL, 0, Integer.MAX_VALUE, CheckTotalExtractor.getInstance()));
        List<Long> taskIds = Lists.newArrayList(Collections2.transform(groupedTasks.values(), new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel taskModel) {
                return taskModel.getId();
            }
        }));
        Map<Long, Long> taskId2PiIdCache = processQueryService.getProcessInstanceIdsByTaskIds(taskIds);
        Multimap<Long, TaskModel> piTasks = processQueryService.getTaskModelsByProcessInstanceIdsAndStates(
                Lists.newArrayList(taskId2PiIdCache.values()), Arrays.asList(State.COMPLETION, State.VERIFICATION));
        Map<Long, String> piId2RegionIdCache = Maps.newHashMap();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            String rid = (String) entry.getKey();
            TaskModel task = entry.getValue();
            piId2RegionIdCache.put(taskId2PiIdCache.get(task.getId()), rid);
        }
        for (Map.Entry<Long, TaskModel> entry : piTasks.entries()) {
            TaskModel task = entry.getValue();
            String rid = piId2RegionIdCache.get(entry.getKey());
            groupedTasks.put(rid, task);
        }
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(Lists.newArrayList(taskIds));
        Map<String, Map<State, List<Tuple3<Date, Date, Date>>>> ret = Maps.newHashMap();
        for (Map.Entry<Object, TaskModel> entry : groupedTasks.entries()) {
            String rid = (String) entry.getKey();
            TaskModel task = entry.getValue();
            StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
            if (t != null) {
                State state = StateFactory.getStateByTaskName(task.getName());
                Date createDate = task.getCreatedOn();
                Date deadline = StateEndTimeUtil.getEndDate(state.getId(), createDate);
                Date operationDate = t.getEndDate();
                Date comparedDate = operationDate == null ? new Date() : operationDate;
                if (deadline != null && deadline.before(comparedDate)) {
                    String userId = task.getUser() == null ? "UNASSIGNED" : task.getUser();
                    Map<State, List<Tuple3<Date, Date, Date>>> map = ret.get(userId);
                    if (map == null) {
                        map = Maps.newHashMap();
                        List<Tuple3<Date, Date, Date>> list = Lists.newLinkedList();
                        list.add(new Tuple3<Date, Date, Date>(createDate, deadline, operationDate));
                        map.put(state, list);
                    } else {
                        List<Tuple3<Date, Date, Date>> list = map.get(state);
                        list.add(new Tuple3<Date, Date, Date>(createDate, deadline, operationDate));
                    }
                    ret.put(userId, map);
                }
            }
        }
        return ret;
    }

    @Override
    public List<String> getApplyIdsByStateDuring(int stateId, Date from, Date to) {
        State state = StateFactory.getStateById(stateId);
        List<TaskModel> tasks = processQueryService.listAllTasksByStates(Arrays.asList(state));
        List<Long> taskIds = Lists.transform(tasks, new Function<TaskModel, Long>() {
            @Override
            public Long apply(TaskModel task) {
                return task.getId();
            }
        });
        Map<Long, StartAndEndDateOfTaskTuple> datesMap = processQueryService.getStartAndEndDatesByTasks(Lists.newArrayList(taskIds));
        List<String> ret = Lists.newLinkedList();
        for (TaskModel task : tasks) {
            StartAndEndDateOfTaskTuple t = datesMap.get(task.getId());
            if (t != null && t.getEndDate() != null && isDateSatisfied(t.getEndDate(), from, to)) {
                ret.add(task.getCheckTotal().getId());
            }
        }
        return ret;
    }

    private boolean isDateSatisfied(Date date, Date from, Date to) {
        boolean ret = true;
        if (from != null) {
            if (date.before(from)) {
                ret = false;
            }
        }
        if (to != null) {
            if (date.after(to)) {
                ret = false;
            }
        }
        return ret;
    }
}
